/******************************************************************************

	COPYRIGHT(C) JONAS 'SORTIE' TERMANSEN 2008, 2009, 2010.

	This file is part of Maxsi Engine.

	Maxsi Engine is free software: you can redistribute it and/or modify it
	under the terms of the GNU Lesser General Public License as published by
	the Free Software Foundation, either version 3 of the License, or (at your
	option) any later version.

	Maxsi engine is distributed in the hope that it will be useful, but WITHOUT
	ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
	FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
	for more details.

	You should have received a copy of the GNU Lesser General Public License
	along with Maxsi Engine. If not, see <http://www.gnu.org/licenses/>.

	MaxsiEngine.dll
	A general purpose C++ library for the Maxsi Technology Project.

	MaxsiList.cpp
	A system for handling BYTE** based lists.

******************************************************************************/

#include "MaxsiEngine.h"

#ifndef __GNUC__ // Hacks to make Maxsi Engine compile using MinGW
#pragma warning(disable: 4267) // Perhaps rewrite the code so it never makes a warning in the first place?
#endif

#ifdef	_DEBUG
#define CRASH() delete[] (char*)"CRASHME!"
#else
#define CRASH()
#endif

BeginMaxsiNamespace

MAXSI_REMOVE_OLD_CODE();
#if 0
// Deprecated by AddListEntry_s due to security concerns when memory
// allocation fails in SetListSize or SetListEntry.
List AddListEntry(List list, BYTE* Buffer, size_t BufferSize)
{
	// If list isn't a valid List yet, then create one! :D
	if (!list)
	{
		list		=	CreateList(0);
	}
	
	// If memory allocation failed return NULL
	if ( list == NULL ) { return NULL; }

	// Get the current list size
	size_t	ListSize	=	GetListSize(list);

	// Increase the list size with one and add a new entry!
	SetListSize(list,GetListSize(list)+1);
	SetListEntry(list,ListSize,Buffer,BufferSize);

	return list;
}
#endif

bool AddListEntry(List* list, BYTE* Buffer, size_t BufferSize)
{
	// If list isn't a valid pointer return false
	if (!list) { return false; }

	// If list isn't allocated yet, allocate a null-size list.
	if (!(*list))
	{
		*list		=	CreateList(0);
	}
	
	// If memory allocation failed return NULL
	if ( list == NULL ) { return NULL; }

	// Get the current list size
	size_t	ListSize	=	GetListSize(*list);

	// Increase the list size with one and add a new entry!
	if ( SetListSize(*list,GetListSize(*list)+1) == false ) { return false; }
	MAXSI_TODO("We have a memory allocation/security concern here! If SetListSize succeeds, but SetListEntry does not, what does that mean?");
	if ( SetListEntry(*list,ListSize,Buffer,BufferSize) == false ) { return false; }

	// If the above worked, then great!
	return true;
}

bool AddListEntryA(List* list, char* String)
{
	return AddListEntry(list, (BYTE*)String, sizeof(char)*(strlen(String)+1));
}

bool AddListEntryW(List* list, wchar_t* String)
{
	return AddListEntry(list, (BYTE*)String, sizeof(wchar_t)*(wcslen(String)+1));
}

BYTE* BufferListToBuffer(List BufferList)
{
	// Find the amount of entries in the list
	size_t	BufferListSize		=	GetListSize(BufferList);
	size_t	TotalBufferSize		=	0;

	// Sum up the sizes of each buffer in the list
	for (size_t N = 0; N < BufferListSize; N++)
	{
		TotalBufferSize		+=	GetListEntrySize(BufferList,N);
	}
	
	// Allocate a buffer big enough to store all the buffers in the list
	BYTE* TotalBuffer		=	new BYTE[TotalBufferSize];

	// Return NULL if memory allocation failed!
	if ( TotalBuffer == NULL ) { return NULL; }

	// Initialize variables
	size_t Offset = 0;

	// Gather each buffer in the list and copy it to the continuous buffer 
	for (size_t N = 0; N < BufferListSize; N++)
	{
		BYTE*	Data		=	GetListEntry	(BufferList,(size_t)N);
		size_t	Data_Size	=	GetListEntrySize(BufferList,(size_t)N);
		memcpy(TotalBuffer+Offset,Data,Data_Size);
		Offset+=Data_Size;
	}

	return TotalBuffer;
}
BYTE* BufferListToBufferN(List BufferList)
{
	// Find the amount of entries in the list
	size_t	BufferListSize		=	GetListSize(BufferList);
	size_t	TotalBufferSize		=	0;

	// Sum up the sizes of each buffer in the list
	for (size_t N = 0; N < BufferListSize; N++)
	{
		TotalBufferSize		+=	GetListEntrySize(BufferList,N);
	}
	
	// Allocate a buffer big enough to store all the buffers in the list
	BYTE* TotalBuffer		=	new BYTE[TotalBufferSize+1];

	// Return NULL if memory allocation failed!
	if ( TotalBuffer == NULL ) { return NULL; }

	// Initialize variables
	size_t Offset = 0;

	// Gather each buffer in the list and copy it to the continuous buffer 
	for (size_t N = 0; N < BufferListSize; N++)
	{
		BYTE*	Data		=	GetListEntry	(BufferList,(size_t)N);
		size_t	Data_Size	=	GetListEntrySize(BufferList,(size_t)N);
		memcpy(TotalBuffer+Offset,Data,Data_Size);
		Offset+=Data_Size;
	}

	// We null-terminate the generated buffer
	TotalBuffer[Offset] = 0;

	return TotalBuffer;
}

char* StringListToBufferA(List BufferList)
{
	// Find the amount of entries in the list
	size_t	BufferListSize		=	GetListSize(BufferList);
	size_t	TotalBufferSize		=	0;

	// Sum up the sizes of each buffer in the list
	for (size_t N = 0; N < BufferListSize; N++)
	{
		TotalBufferSize		+=	strlen(GetListEntryA(BufferList,N));
	}
	
	// Allocate a buffer big enough to store all the buffers in the list
	char* TotalBuffer		=	new char[TotalBufferSize+1];

	// Return NULL if memory allocation failed!
	if ( TotalBuffer == NULL ) { return NULL; }

	// Initialize variables
	size_t Offset = 0;

	// Append each string to our zero-length buffer!
	for (size_t N = 0; N < BufferListSize; N++)
	{
		char*		String		=	GetListEntryA(BufferList,(size_t)N);
		size_t		StringSize	=	strlen(String);

		memcpy(TotalBuffer+Offset,String,StringSize*sizeof(char));
		Offset	+=	StringSize;
	}

	// We null-terminate the generated buffer
	TotalBuffer[Offset] = 0;

	return TotalBuffer;
}

wchar_t* StringListToBufferW(List BufferList)
{
	// Find the amount of entries in the list
	size_t	BufferListSize		=	GetListSize(BufferList);
	size_t	TotalBufferSize		=	0;

	// Sum up the sizes of each buffer in the list
	for (size_t N = 0; N < BufferListSize; N++)
	{
		TotalBufferSize		+=	wcslen(GetListEntryW(BufferList,N));
	}
	
	// Allocate a buffer big enough to store all the buffers in the list
	wchar_t* TotalBuffer		=	new wchar_t[TotalBufferSize+1];

	// Return NULL if memory allocation failed!
	if ( TotalBuffer == NULL ) { return NULL; }

	// Initialize variables
	size_t Offset = 0;

	// Append each string to our zero-length buffer!
	for (size_t N = 0; N < BufferListSize; N++)
	{
		wchar_t*	String		=	GetListEntryW(BufferList,(size_t)N);
		size_t		StringSize	=	wcslen(String);

		memcpy(TotalBuffer+Offset,String,StringSize*sizeof(wchar_t));
		Offset	+=	StringSize;
	}

	// We null-terminate the generated buffer
	TotalBuffer[Offset] = 0;

	return TotalBuffer;
}

List BuildStringListA(size_t NumParameters, ...)
{
	List	Out				=	CreateList(NumParameters);
	va_list param_pt;

	va_start(param_pt,NumParameters); // Call the setup macro
	
	char*	String			=	0;

	for (size_t I = 0 ; I < NumParameters ; I++)
	{
		String = va_arg(param_pt,char*);
		if ( String )
		{
			SetListEntryA(Out,I,String);
		}
	}

	return Out;
}

List BuildStringListW(size_t NumParameters, ...)
{
	List	Out				=	CreateList(NumParameters);
	va_list param_pt;

	va_start(param_pt,NumParameters); // Call the setup macro
	
	wchar_t*	String		=	0;

	for (size_t I = 0 ; I < NumParameters ; I++)
	{
		String = va_arg(param_pt,wchar_t*);
		if ( String )
		{
			SetListEntryW(Out,I,String);
		}
	}

	return Out;
}


List BuildBufferList(size_t NumParameters, ...)
{
	List	Out				=	CreateList(NumParameters);
	va_list param_pt;

	va_start(param_pt,NumParameters); // Call the setup macro

	BYTE*	Buffer			=	0;
	size_t	BufferLength	=	0;
	
	for (size_t I = 0 ; I < NumParameters ; I++)
	{
		Buffer			=	va_arg(param_pt,BYTE*);
		BufferLength	=	va_arg(param_pt,size_t);
		
		SetListEntry(Out,I,Buffer,BufferLength);			
	}

	return Out;
}

List CreateList(size_t Size)
{
	// Allocate a pointer that points to the actual data. This allows us to
	// change the actual data and all references to this pointer (List) will
	// automatically be relocated - Not thread safe, though. You have to implement
	// your own thread saftely mechanisms upon these functions.

	List	Result			=	new ListC;

	// See if the memory allocation failed
	if ( Result == NULL ) { return NULL; }

	// Allocate the pointer table needed for Size entries!
	ListC	Contents		=	CreateListC(Size);

	// See if the memory allocation succeded
	if ( Contents == NULL ) { delete Result; return NULL; }

	// Set our List pointer to the pointer table
	Result[0]				=	Contents;

	// Return a pointer to the pointer that points to the pointer table that points to the actual data.
	// (For instance, I have 4 buffers. I then allocate a pointer table, which is a buffer that stores
	// a) the number of entries in the List b) for each entry a Pointer to the actual data and c) for
	// each entry the size of the entry in bytes. If I need to add another entry to the pointer table,
	// I just allocate a longer version of it, copy some data, and delete the old pointer table. All
	// without dealing with the actual data. If the same list is referenced multiple times, then I
	// would have to do this to each copy of them, so instead the List data type pointers to a pointer
	// to the Pointer Table. When I reallocate the pointer table, I also change this pointer, which
	// then redirects all references to the pointer table. To easily go through all these abstraction
	// layers I simply use functions that take the List data type and the entry number in the pointer
	// table, and then functions then gets and sets the actual data as needed.)

	return Result;
}

ListC CreateListC(size_t Size)
{
	// Calculate the required size of the buffer - We have room for some damn huge
	// buffers here on 64-bit systems where size_t is 64-bit!
	size_t	ContentsLen		=	sizeof(size_t)+Size*(sizeof(size_t)+sizeof(BYTE*));
	
	// Allocate the memory required to store pointers to the individual list entries
	// here. The actual memory of the lists are not stored here, which allows us to
	// move stuff around, and to easily add more entries without the need to handle
	// huge data buffers.
	ListC	Contents		=	new BYTE[ContentsLen];

	// If the pointer data allocation failed, delete the pointer to it, and 
	if ( Contents == NULL ) { return NULL; }

	// Reset the entire pointer table to zero
	memset(Contents, 0, ContentsLen);
	
	// Set the first sizeof(size_t) bytes of the pointer table to the amount of entries
	((size_t*)(Contents))[0]	=	Size;

	return Contents;
}

// Makes a longer / shorter pointer table, copies as many pointers to it as possible from the old list,
// and changes the ListC pointer to the new index buffer. Or something clever.
bool SetListSize(List list, size_t NewSize)
{
	// Find the size of the old pointer table!
	size_t	OldSize		=	GetListSize(list);
	ListC	OldContent	=	*list;

	// Allocate a new Pointer Table with room for NewSize entries in it!
	ListC	NewContent	=	CreateListC(NewSize);
	size_t	MinSize		=	(OldSize > NewSize) ? NewSize : OldSize;

	// Copy parts of the index
	if ( MinSize )
	{
		for (size_t N = 0; N < MinSize; N++)
		{
			// Find the current values for those we keep
			size_t	EntrySize	=	GetListEntrySize(list,N);
			BYTE*	Pointer		=	GetListEntry(list,N);

			// Copy the values to the new pointer table!
			memcpy(NewContent+sizeof(size_t)+N*(sizeof(size_t)+sizeof(BYTE*)),&EntrySize,sizeof(EntrySize));
			memcpy(NewContent+sizeof(size_t)+sizeof(size_t)+N*(sizeof(BYTE*)+sizeof(size_t)),&Pointer,sizeof(Pointer));
		}
	}

	// Delete the contents of the now-forgotten pointers
	if ( OldSize > NewSize )
	{
		for (size_t N = NewSize; N < OldSize; N++)
		{
			// Delete the buffer if it isn't used any longer!
			if ( GetListEntry(list,N) )
			{
				delete[]	GetListEntry(list,N);
			}
		}
	}

	*list	=	NewContent;
	delete[]	OldContent;
	return true;
}

bool DeleteList(List list)
{
	// See if list is a NULL-Pointer
	if ( !list ) { return true; }

	// Find the amount of list entries and cache it
	size_t ListSize		=	GetListSize(list);

	// Go through each entry and delete[] it
	for (size_t I = 0; I < ListSize; I++)
	{
		if ( GetListEntry(list,(size_t)I) )
		{
			delete[] GetListEntry(list,(size_t)I);
		}
	}

	delete[]	*list;
	delete		list;

	return true;
}

BYTE* GetListEntry(List list, size_t Number)
{
	if (Number>GetListSize(list)) { return NULL; }

	// Lookup the pointer we need!
	BYTE** Source = (BYTE**)(*list+sizeof(size_t)+sizeof(size_t)+Number*(sizeof(BYTE*)+sizeof(size_t)));

	return Source[0];
}

char* GetListEntryA(List list, size_t Number)
{
	return (char*)GetListEntry(list, Number);
}

wchar_t* GetListEntryW(List list, size_t Number)
{
	return (wchar_t*)GetListEntry(list, Number);
}

bool SetListEntry(List list, size_t Number, BYTE* Buffer, size_t BufferSize)
{
	// See if list is NULL
	if ( list == NULL ) { return false; }

	// Protect against buffer overruns
	if (Number>GetListSize(list)) {	return false; }

	// Allocate enough space to store Buffer's BufferSize bytes!
	char* TMP = new char[BufferSize];

	// Check if the memory allocation failed, which it easily could with large
	// buffer, especially since BufferSize is 64-bit on 64-bit systems, and that's
	// just about 16 exabytes of ram. (16 * 1024^6 bytes!)
	// I think this system should be able to at least allocate a gigabyte RAM on
	// systems that have it spare. But this isn't a problem for any of our uses
	// in the Maxsi Project at the moment.
	if ( TMP == NULL ) { return false; }

	// Store Buffer in TMP.
	memcpy(TMP,Buffer,BufferSize);

	// Update the pointer table to contain a pointer to TMP and store the size of TMP.
	memcpy(*list+sizeof(size_t)+Number*(sizeof(size_t)+sizeof(char*)),&BufferSize,sizeof(BufferSize));
	memcpy(*list+sizeof(size_t)+sizeof(size_t)+Number*(sizeof(char*)+sizeof(size_t)),&TMP,sizeof(TMP));

	return true;
}

bool SetListEntryA(List list, size_t Number, char* String)
{
	// Find the size of the string and forward it to SetListEntry.
	return SetListEntry(list, Number, (BYTE*)String, sizeof(char)*(strlen(String)+1));
}

bool SetListEntryW(List list, size_t Number, wchar_t* String)
{
	// Find the size of the string and forward it to SetListEntry.
	return SetListEntry(list, Number, (BYTE*)String, sizeof(wchar_t)*(wcslen(String)+1));
}

size_t GetListSize(List list)
{
	// Check if list is a null pointer.
	if (!list) { return 0; }

	// Return the first size_t in the Pointer Table.
	return *(size_t*)(*list);
}

size_t GetListEntrySize(List list, size_t Number)
{
	// Check if list is a null pointer.
	if (!list) { return 0; }

	// Check for a possible buffer overrun.
	if (Number>GetListSize(list)) { return 0; }

	// Return the size_t located at sizeof(size_t) + Number * (sizeof(size_t)+sizeof(BYTE*)
	// in the Pointer Table.
	return (size_t)*(*list + sizeof(size_t) + Number * (sizeof(size_t)+sizeof(BYTE*)));
}

List DuplicateList(List list)
{
	// Create a list the same size.
	size_t	ListSize	=	GetListSize(list);
	List	NewList		=	CreateList(ListSize);

	// Check for memory allocation failures.
	if ( NewList == NULL ) { return NULL; }

	if ( ListSize )
	{
		for (size_t N = 0; N < ListSize; N++)
		{
			// Copy each entry, and see if it failed, if so, delete it all and return NULL.
			if ( SetListEntry(NewList,N,(BYTE*)GetListEntry(list,N),GetListEntrySize(list,N)) == false )
			{
				DeleteList(NewList);
				return NULL;
			}
		}
	}
	
	// Success, return the new List.
	return NewList;
}

EndMaxsiNamespace
